package il.ac.tau.cvws.scrabblesidekick.client.app.controller;

import il.ac.tau.cvws.scrabblesidekick.client.app.AppConstants;
import il.ac.tau.cvws.scrabblesidekick.client.engine.CameraEngine;
import il.ac.tau.cvws.scrabblesidekick.client.ui.CameraActivity;
import il.ac.tau.cvws.scrabblesidekick.client.ui.ScrabbleSidekickActivity;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.ImageData;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.OrientationData;
import il.ac.tau.cvws.scrabblesidekick.shared.util.math.Point;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import android.content.Context;
import android.graphics.Bitmap;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Base64;

/* This class is intended as a base controller/bridge between the upper platform's
 * two UI controllers (CalibrationActivity and GameActivity) and the lower engine's
 * logic (CalibrationEngine and GameEngine), both relying on the camera.
 */
public abstract class CameraController extends ScrabbleSidekickController implements SensorEventListener
{
	/* The accelerometer is used in order to give the image processing engine
	 * in the server more information about the orientation of the device at the
	 * time the board's picture was snapped. It is redundant when using the TILTED
	 * board detection mode.
	 */
	private static final boolean USE_ACCELEROMETER = false;
	
	protected final CameraEngine _engine;		// The applicative engine
	
	private OrientationData _lastOrientation;	// The last detected orientation of the device
	private boolean  		_isPlacedCorrectly;	// Flag which determines whether or not the camera
												// is placed correctly before snapping a high
												// quality image.
	
	public CameraController(ScrabbleSidekickActivity activity)
	{
		super(activity);
		
		_engine = createEngine();
		
		_lastOrientation = new OrientationData(0, 0, 0);
		
		if (USE_ACCELEROMETER)
		{
			initSensorManager();
		}
	}

	/* To be overridden by subclasses. This method should create and return
	 * the appropriate engine implementation to the controller.
	 */
	protected abstract CameraEngine createEngine();
	
	@Override
	public void onAccuracyChanged(Sensor sensor,int accuracy)
	{
		// Do nothing
	}

	/* Accelerometer notification that the device's orientation has changed.
	 * Make sure it is actually the accelerometer's message and update the
	 * orientation accordingly.
	 */
	@Override
	public void onSensorChanged(SensorEvent event)
	{
		if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
		{			
			float x = event.values[0];
			float y = event.values[1];
			float z = event.values[2];
			
			_lastOrientation.setX(x);
			_lastOrientation.setY(y);
			_lastOrientation.setZ(z);
		}
	}
	
	/* Handle a preview image. This happens every second.
	 */
	public boolean handleImagePosition(Bitmap image, Point[] border)
	{
		/* If the preview image has already been detected to be positioned
		 * correctly, we don't need to handle it again.
		 */
		if (_isPlacedCorrectly)
		{
			return false;
		}
		
		/* Test whether or not the preview is positioned correctly.
		 */
		_isPlacedCorrectly = _engine.isPlacedOnBoard(image, border);
		
		return _isPlacedCorrectly;
	}
	
	/* Handles the high-quality image, after being taken by the device's camera once the preview
	 * algorithms detect that the camera is positioned correctly.
	 */
	public void handleHighQualityImage(final int screenWidth, final int screenHeight, final Bitmap image)
	{
		ByteArrayOutputStream bao = new ByteArrayOutputStream();
		
		try
		{
			/* Compress the image, encode it as a string, recycle the image data, and
			 * pass it on to the controller's implementation for handling.
			 */
			image.compress(Bitmap.CompressFormat.JPEG, AppConstants.JPEG_QUALITY, bao);
			String imgStr = Base64.encodeToString(bao.toByteArray(), Base64.DEFAULT);
			
			image.recycle();
			
			internalHandleHighQualityImage(screenWidth, screenHeight, new ImageData(imgStr, _lastOrientation));
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				bao.close();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}
	
	/* Initialize the SensorManager for the accelerometer. The method initializes the
	 * manager and registers this class itself as a listener for accelerometer events.
	 */
	private void initSensorManager()
	{
		SensorManager sensorManager = (SensorManager)_activity.getSystemService(Context.SENSOR_SERVICE);
		
		sensorManager.registerListener(this,
				sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
				SensorManager.SENSOR_DELAY_NORMAL);
	}
	
	/* In any case of engine failure, this method passes the failure messages on
	 * to the platform's activity for handling.
	 */
	public void handleFailure(String msg)
	{
		((CameraActivity)_activity).handleFailure(msg);
	}
	
	/* This abstract method is meant for concrete sub-classes to implement, for handling
	 * the high-quality image once it arrives.
	 */
	protected abstract void internalHandleHighQualityImage(
			int screenWidth, int screenHeight, ImageData data);	
}
